/*!
\copyright  Copyright (c) 2024 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Implementation of Serial Port Profile Loopback test application.
*/

#ifdef INCLUDE_SPPS_LOOPBACK_TEST
#include "spps_test.h"
#include "spps_test_private_data.h"
#include "spp_lib.h"
#include <connection.h>
#include <logging.h>
#include <sink.h>
#include <source.h>
#include <stream.h>
#include <vm.h>
#include <cm_lib.h>
#include <stdlib.h>

#ifdef GC_SECTIONS
    /* Move all functions in KEEP_PM section to ensure they are not removed during
     * garbage collection */
    #pragma unitcodesection KEEP_PM
#endif

/*! \brief Message handler for SPP loopback app.*/
static void sppTest_taskHandler(Task task, MessageId id, Message msg);
/*! \brief Message handler for SPP library messages from Synergy.*/
static void sppTest_HandleSppPrim(Task task, Message message);
/*! \brief Message handler for Connection Library messages from Synergy.*/
static void sppTest_HandleCmPrim(Task task, Message message);
/*! \brief Activate SPP service with a custom service record for respective instances.*/
static void sppTest_ActivateSpp(spp_service_data_t* spp_service);
/*! \brief Handler for SPP service activate confirmation.*/
static void sppTest_HandleSppActivateCfm(Task task, CsrBtSppActivateCfm *cfm);
/*! \brief Handler for SPP service deactivate confirmation.*/
static void sppTest_HandleSppDeactivateCfm(Task task, CsrBtSppDeactivateCfm *cfm);
/*! \brief Handler for instance qid confirmation.*/
static void sppTest_HandleSppGetInstancesQidCfm(Task task, CsrBtSppGetInstancesQidCfm *cfm);
/*! \brief Handler for instance qid confirmation.*/
static uint16 sppTest_GetServiceRecordSizeByInstIndex(uint8 spp_instance_index);
/*! \brief Handler for Incoming Connect indication.*/
static void sppTest_HandleSppConnectInd(Task task, CsrBtSppConnectInd *ind);
/*! \brief Handler for Disconnect indication.*/
static void sppTest_HandleSppDisconnectInd(Task task, CsrBtSppDisconnectInd *ind);
/*! \brief Handler for Port negotiation req.*/
static void sppTest_HandleSppPortNegInd(Task task, CsrBtSppPortnegInd *ind);
/*! \brief Handler for More data from SPP RFCOMM Streams.*/
static void sppTest_HandleMessageMoreData(MessageMoreData *message);
/*! \brief Retrieve SPP Connection id from the RFCOMM Source.*/
static uint16 sppTest_RetrieveConnectionIdFromSource(Source src);
/*! \brief loop data from RFCOMM source back onto the RFCOMM sink end point.*/
static void sppTest_FlushDataFromSink(uint16 conn_id);

spp_service_data_t *spp_service_test_data[spp_max_instances] = {0, 0};

/*SPP task data block */
static TaskData spp_test_taskdata = {sppTest_taskHandler};

static const uint8 spps_service_record_instance1[] = {
0x09, 0x00, 0x01,           /*  0  1  2  ServiceClassIDList(0x0001) */
0x35,    3,                 /*  3  4     DataElSeq 3 bytes */
0x19, 0x11, 0x01,           /*  5  6  7  UUID SerialPort(0x1101) */
0x09, 0x00, 0x04,           /*  8  9 10  ProtocolDescriptorList(0x0004) */
0x35,   12,                 /* 11 12     DataElSeq 12 bytes */
0x35,    3,                 /* 13 14     DataElSeq 3 bytes */
0x19, 0x01, 0x00,           /* 15 16 17  UUID L2CAP(0x0100) */
0x35,    5,                 /* 18 19     DataElSeq 5 bytes */
0x19, 0x00, 0x03,           /* 20 21 22  UUID RFCOMM(0x0003) */
0x08, 0x01,  /* 23 24     uint8 RFCOMM channel */
0x09, 0x00, 0x06,           /* 25 26 27  LanguageBaseAttributeIDList(0x0006) */
0x35,    9,                 /* 28 29     DataElSeq 9 bytes */
0x09,  'e',  'n',           /* 30 31 32  Language: English */
0x09, 0x00, 0x6A,           /* 33 34 35  Encoding: UTF-8 */
0x09, 0x01, 0x00,           /* 36 37 38  ID base: 0x0100 */
0x09, 0x01, 0x00,           /* 39 40 41  ServiceName 0x0100, base + 0 */
0x25,   4,
'S',  'P',  'P', '1',
0x09, 0x00, 0x09,           /* 48 49 50  BluetoothProfileDescriptorList(0x0009) */
0x35, 0x08,                 /* 51 52     DataElSeq 8 bytes [List size] */
0x35, 0x06,                 /* 53 54     DataElSeq 6 bytes [List item] */
0x19, 0x11, 0x01,           /* 55 56 57  UUID SerialPort(0x1101) */
0x09, 0x01, 0x02,           /* 58 59 60  SerialPort Version (0x0102) */
};

static const uint8 spps_service_record_instance2[] = {
0x09, 0x00, 0x01,           /*  0  1  2  ServiceClassIDList(0x0001) */
0x35,    3,                 /*  3  4     DataElSeq 3 bytes */
0x19, 0x11, 0x01,           /*  5  6  7  UUID SerialPort(0x1101) */
0x09, 0x00, 0x04,           /*  8  9 10  ProtocolDescriptorList(0x0004) */
0x35,   12,                 /* 11 12     DataElSeq 12 bytes */
0x35,    3,                 /* 13 14     DataElSeq 3 bytes */
0x19, 0x01, 0x00,           /* 15 16 17  UUID L2CAP(0x0100) */
0x35,    5,                 /* 18 19     DataElSeq 5 bytes */
0x19, 0x00, 0x03,           /* 20 21 22  UUID RFCOMM(0x0003) */
0x08, 0x02,  /* 23 24     uint8 RFCOMM channel */
0x09, 0x00, 0x06,           /* 25 26 27  LanguageBaseAttributeIDList(0x0006) */
0x35,    9,                 /* 28 29     DataElSeq 9 bytes */
0x09,  'e',  'n',           /* 30 31 32  Language: English */
0x09, 0x00, 0x6A,           /* 33 34 35  Encoding: UTF-8 */
0x09, 0x01, 0x00,           /* 36 37 38  ID base: 0x0100 */
0x09, 0x01, 0x00,           /* 39 40 41  ServiceName 0x0100, base + 0 */
0x25,   4,
'S',  'P',  'P', '2',
0x09, 0x00, 0x09,           /* 48 49 50  BluetoothProfileDescriptorList(0x0009) */
0x35, 0x08,                 /* 51 52     DataElSeq 8 bytes [List size] */
0x35, 0x06,                 /* 53 54     DataElSeq 6 bytes [List item] */
0x19, 0x11, 0x01,           /* 55 56 57  UUID SerialPort(0x1101) */
0x09, 0x01, 0x02,           /* 58 59 60  SerialPort Version (0x0102) */
};

static uint16 sppTest_GetServiceRecordSizeByInstIndex(uint8 spp_instance_index)
{
    uint16 size_spp_service_record = 0;
    PanicFalse(spp_instance_index < spp_max_instances);

    switch(spp_instance_index)
    {
        case spp_server_instance_1:
        {
            size_spp_service_record = sizeof(spps_service_record_instance1);
        }
        break;
        case spp_server_instance_2:
        {
            size_spp_service_record = sizeof(spps_service_record_instance2);
        }
        break;
        default:
        {
            DEBUG_LOG_ERROR("sppTest_GetServiceRecordSizeByInstIndex Unrecognised index=%d", spp_instance_index);
        }
        break;
    }

    return size_spp_service_record;

}

static void sppTest_ActivateSpp(spp_service_data_t* spp_service)
{
    uint8 *spp_record = NULL;
    uint16 spp_record_size = 0;
    const uint8 *service_record = NULL;

    if(!spp_service)
    {
        DEBUG_LOG_ERROR("sppTest_ActivateSpp, Invalid service data ");
        return;
    }

    switch(spp_service->spp_instance_id)
    {
        case spp_server_instance_1:
        {
            service_record = spps_service_record_instance1;
        }
        break;
        case spp_server_instance_2:
        {
            service_record = spps_service_record_instance2;
        }
        break;
        default:
        {
            DEBUG_LOG_ERROR("sppTest_ActivateSpp Unrecognised index=%d", spp_service->spp_instance_id);
            Panic();
        }
        break;
    }

    spp_record_size = sppTest_GetServiceRecordSizeByInstIndex(spp_service->spp_instance_id);

    if (spp_record_size)
    {
        spp_record = CsrPmemAlloc(spp_record_size);
        CsrMemCpy(spp_record, service_record, spp_record_size);

        DEBUG_LOG_INFO("sppTest_ActivateSpp id:%d", spp_service->spp_queue_id);

        SppActivateReqSendExt(spp_service->spp_queue_id,
                           &spp_service->spp_service_handler_task,
                           CSR_BT_INFINITE_TIME,
                           spp_record,
                           spp_record_size,
                           SPP_SERVICE_RECORD_CHANNEL_INDEX,
                           SECL4_IN_LEVEL_2,
                           SPP_CLASS_OF_DEVICE
                           );
    }

}

static void sppTest_HandleSppGetInstancesQidCfm(Task task, CsrBtSppGetInstancesQidCfm *cfm)
{
    DEBUG_LOG_INFO("sppTest_HandleSppGetInstancesQidCfm, phandlesListSize:%d",
                       cfm->phandlesListSize);

    if (cfm->phandlesListSize && cfm->phandlesList)
    {
        spp_service_data_t* spp_service = (spp_service_data_t*) task;
        int index = 0;

        if(!spp_service)
        {
            DEBUG_LOG_ERROR("sppTest_HandleSppGetInstancesQidCfm, Invalid service data ");
            return;
        }

        index = spp_service->spp_instance_id;
        /* Check if index of the instance is within the Handles list size*/
        if (index <= (cfm->phandlesListSize - 1))
        {
            spp_service->spp_queue_id = cfm->phandlesList[index];

            DEBUG_LOG_INFO("sppTest_HandleSppGetInstancesQidCfm, Queue index:%d, id:%d",
                               index, spp_service->spp_queue_id);

            sppTest_ActivateSpp(spp_service);
        }
        else
        {
            DEBUG_LOG_ERROR("sppTest_HandleSppGetInstancesQidCfm Insufficient SPP instances, max=%d",
                                cfm->phandlesListSize);
        }
    }

}

static void sppTest_HandleSppActivateCfm(Task task, CsrBtSppActivateCfm *cfm)
{
    DEBUG_LOG_INFO("sppTest_HandleSppActivateCfm, serverChannel:%d",
                       cfm->serverChannel);

    spp_service_data_t* spp_service = (spp_service_data_t*) task;

    if(!spp_service)
    {
        DEBUG_LOG_ERROR("sppTest_HandleSppActivateCfm, Invalid service data ");
        return;
    }

    spp_service->local_server_channel = cfm->serverChannel;
}

static void sppTest_HandleSppDeactivateCfm(Task task, CsrBtSppDeactivateCfm *cfm)
{
    int instance_id = 0;
    DEBUG_LOG_INFO("sppTest_HandleSppDeactivateCfm, reason=0x%04X,Supplier=0x%04X",
                       cfm->resultCode, cfm->resultSupplier);

    spp_service_data_t* spp_service = (spp_service_data_t*) task;

    if(!spp_service)
    {
        DEBUG_LOG_ERROR("sppTest_HandleSppDeactivateCfm, Invalid service data ");
        return;
    }

    spp_service->local_server_channel = 0;
    instance_id = spp_service->spp_instance_id;
    free(spp_service);
    spp_service_test_data[instance_id] = 0;
    DEBUG_LOG_INFO("sppTest_HandleSppDeactivateCfm: SPP Service Task deleted.");

}

static void sppTest_HandleSppConnectInd(Task task, CsrBtSppConnectInd *ind)
{
    DEBUG_LOG_INFO("sppTest_HandleSppConnectInd,"
                       "resultCode:0x%X, Supplier=0x%X, btConnId=0x%X",
                       ind->resultCode, ind->resultSupplier,ind->btConnId);

    if (ind->resultCode == CSR_BT_RESULT_CODE_SPP_SUCCESS)
    {
        spp_service_data_t* spp_service = (spp_service_data_t*) task;
        spp_service->spp_connection_id = CM_GET_UINT16ID_FROM_BTCONN_ID(ind->btConnId);
        sppTest_ConfigureSppStreams(spp_service->spp_instance_id);
    }

}

static void sppTest_HandleSppDisconnectInd(Task task, CsrBtSppDisconnectInd *ind)
{
    spp_service_data_t* spp_service = (spp_service_data_t*) task;

    DEBUG_LOG_INFO("sppTest_HandleSppDisconnectInd, reason=0x%04X, Supplier=0x%04X, spp_ch=%d",
                       ind->reasonCode, ind->reasonSupplier, spp_service->spp_instance_id);
    spp_service->spp_connection_id = SPP_INVALID_CONN_ID;
    sppTest_ActivateSpp(spp_service);

}

static void sppTest_HandleSppPortNegInd(Task task, CsrBtSppPortnegInd *ind)
{
    spp_service_data_t* spp_service = (spp_service_data_t*) task;

    DEBUG_LOG_INFO("sppTest_HandleSppPortnegInd");

    if (ind->request)
    {
        RFC_PORTNEG_VALUES_T portPar;
        CsrBtPortParDefault(&portPar);
        SppPortnegResSend(spp_service->spp_queue_id,
                          ind->serverChannel,
                          &portPar);
    }
    else
    {
        SppPortnegResSend(spp_service->spp_queue_id,
                          ind->serverChannel,
                          &ind->portPar);
    }
}

static void sppTest_HandleSppPrim(Task task, Message message)
{
    CsrBtSppPrim *prim = (CsrBtSppPrim*) message;

    DEBUG_LOG_INFO("sppTest_HandleSppPrim: 0x%04x\n", *prim);

    switch (*prim)
    {
        case CSR_BT_SPP_GET_INSTANCES_QID_CFM:
            {
                sppTest_HandleSppGetInstancesQidCfm(task, (CsrBtSppGetInstancesQidCfm*)message);
            }
            break;

        case CSR_BT_SPP_ACTIVATE_CFM:
            {
                sppTest_HandleSppActivateCfm(task, (CsrBtSppActivateCfm*)message);
            }
            break;

        case CSR_BT_SPP_DEACTIVATE_CFM:
            {
                sppTest_HandleSppDeactivateCfm(task, (CsrBtSppDeactivateCfm*)message);
            }
            break;

        case CSR_BT_SPP_CONNECT_IND:
            {
                sppTest_HandleSppConnectInd(task, (CsrBtSppConnectInd*)message);
            }
            break;

        case CSR_BT_SPP_DISCONNECT_IND:
            {
                sppTest_HandleSppDisconnectInd(task, (CsrBtSppDisconnectInd*) message);
            }
            break;

        case CSR_BT_SPP_PORTNEG_IND:
            {
                sppTest_HandleSppPortNegInd(task, (CsrBtSppPortnegInd*) message);
            }
            break;

        default:
            {
                DEBUG_LOG_INFO("sppTest_HandleSppPrim Unexpected SPP primitive = 0x%04x", *prim);
            }
            break;
    }
    SppFreeUpstreamMessageContents((void *) message);

}

static void sppTest_HandleCmPrim(Task task, Message message)
{
    UNUSED(task);
    CsrBtCmPrim *prim = (CsrBtCmPrim*) message;

    switch (*prim)
    {
    default:
        {
            DEBUG_LOG_INFO("sppTest_HandleCmPrim Unexpected CM primitive = 0x%04x\n", *prim);
        }
        break;
    }

    CmFreeUpstreamMessageContents((void *) message);

}

static uint16 sppTest_RetrieveConnectionIdFromSource(Source src)
{
    uint16 conn_id = SPP_INVALID_CONN_ID;

    for(uint16 index = 0; index < spp_max_instances; index++)
    {
        if(sppTest_IsInstanceValid(index))
        {
            if(((StreamRfcommSource(spp_service_test_data[index]->spp_connection_id)) == src) &&
                sppTest_IsSppConnIdValid(index))
            {
                conn_id =  spp_service_test_data[index]->spp_connection_id;
                break;
            }
        }
    }
    return conn_id;
}

static void sppTest_FlushDataFromSink(uint16 conn_id)
{
    Sink sink = StreamRfcommSink(conn_id);
    Source src = StreamRfcommSource(conn_id);
    uint16 size = SourceBoundary(src);
    const uint8 *data = SourceMap(src);

    /* Check sink is valid */
    if ( SinkIsValid(sink) )
    {
        /* Get the amount of available space left in the sink */
        uint16 sink_slack = SinkSlack(sink);

        /* Make sure we have enough space for this data */
        if (sink_slack >= size)
        {
            SinkClaim(sink, size);
            uint8 *sink_map = SinkMap(sink);
            memcpy(sink_map, data, size);
            SinkFlush(sink, size);
            DEBUG_LOG("sppTest_FlushDataFromSink %d size flushed from sink", size);
            SourceDrop(src, size);
        }
        else
        {
            // Not enough space in the rfcomm buffer.
            DEBUG_LOG("sppTest_FlushDataFromSink Insufficient space in sink!");
        }
    }
    else
    {
        DEBUG_LOG("sppTest_FlushDataFromSink Sink invalid trying to send data!");
    }
}

static void sppTest_HandleMessageMoreData(MessageMoreData *message)
{
    Source src = message->source;
    uint16 conn_id = sppTest_RetrieveConnectionIdFromSource(src);

    if(!SourceIsValid(src))
    {
        DEBUG_LOG_ERROR("sppTest_HandleMessageMoreData: Invalid Snk or Src");
    }
    else if(conn_id ==  SPP_INVALID_CONN_ID)
    {
        DEBUG_LOG_ERROR("sppTest_HandleMessageMoreData: Channel not connected");
    }
    else
    {
         DEBUG_LOG("sppTest_HandleMessageMoreData %d bytes recieved from src",SourceBoundary(src));
        sppTest_FlushDataFromSink(conn_id);
    }

}

static void sppTest_taskHandler(Task task, MessageId id, Message msg)
{
    switch (id)
    {
    case SPP_PRIM:
        {
            sppTest_HandleSppPrim(task, msg);
        }
        break;
    case CM_PRIM:
        {
            sppTest_HandleCmPrim(task, msg);
        }
        break;

    case MESSAGE_MORE_DATA:
        DEBUG_LOG_INFO("sppTest_taskHandler : Message More Data");
        sppTest_HandleMessageMoreData((MessageMoreData *)msg);
        break;
    case MESSAGE_MORE_SPACE:
        DEBUG_LOG_INFO("sppTest_taskHandler : Message More Space");
        break;
    default:
        break;
    }
}

void SppTest_ServiceStart(uint8 spp_instance_index)
{
    spp_service_data_t *service = NULL;

    PanicFalse(spp_instance_index < spp_max_instances);

    if(sppTest_IsInstanceValid(spp_instance_index))
    {
        DEBUG_LOG_INFO("SppTest_ServiceStart : Service for this instance has been started already");
    }
    else
    {
        service = PanicUnlessNew(spp_service_data_t);
        memset(service, 0, sizeof(spp_service_data_t));
        service->spp_service_handler_task = spp_test_taskdata;
        service->spp_instance_id = spp_instance_index;
        service->spp_connection_id = SPP_INVALID_CONN_ID;
        SppGetInstancesQidReqSend(&service->spp_service_handler_task);
        spp_service_test_data[spp_instance_index] = service;
    }

}

void SppTest_ServiceStop(uint8 spp_instance_index)
{

    PanicFalse(spp_instance_index < spp_max_instances);

    if(sppTest_IsInstanceValid(spp_instance_index))
    {
        SppDeactivateReqSend(sppTest_GetSppQueueIdFromIndex(spp_instance_index),
                                 &sppTest_GetTaskHandler(spp_instance_index));
    }
    else
    {
        DEBUG_LOG_INFO("SppTest_ServiceStopByInstance No instance with index %d", spp_instance_index);
    }

}

void SppTest_Disconnect(uint8 spp_instance_index)
{
    DEBUG_LOG_INFO("SppTest_Disconnect for instance--> %d\n", spp_instance_index);

    if(sppTest_IsInstanceValid(spp_instance_index))
        {
            if(sppTest_IsSppConnIdValid(spp_instance_index))
            {
                SppDisconnectReqSend(sppTest_GetSppQueueIdFromIndex(spp_instance_index),
                                     sppTest_GetSppLocalChannelIdFromIndex(spp_instance_index));
            }
            else
            {
                DEBUG_LOG_INFO("SppTest_ServiceStopByInstance no active connection with instance %d", spp_instance_index);
            }
        }
        else
        {
            DEBUG_LOG_INFO("SppTest_ServiceStopByInstance No instance with index %d", spp_instance_index);
        }

}

void sppTest_ConfigureSppStreams(uint8 spp_instance_index)
{
    Sink sink = StreamRfcommSink(sppTest_GetSppConnIdFromIndex(spp_instance_index));

    if (SinkIsValid(sink))
    {
        DEBUG_LOG_INFO("sppTest_ConfigureSppStreams Success. spp_instance_index=0x%X", spp_instance_index);

        Source src = StreamSourceFromSink(sink);
        MessageStreamTaskFromSink(sink, &sppTest_GetTaskHandler(spp_instance_index));
        MessageStreamTaskFromSource(src, &sppTest_GetTaskHandler(spp_instance_index));
        PanicFalse(SinkConfigure(sink, VM_SINK_MESSAGES, VM_MESSAGES_ALL));
        PanicFalse(SourceConfigure(src, VM_SOURCE_MESSAGES, VM_MESSAGES_ALL));
        PanicFalse(SourceConfigure(src, STREAM_SOURCE_HANDOVER_POLICY, SOURCE_HANDOVER_ALLOW_WITHOUT_DATA));

    }
    else
    {
        DEBUG_LOG_INFO("sppTest_ConfigureSppStreams. sink invalid.");
    }

}

void sppTest_SetSppConnIdForIndex(uint8 spp_instance_index, uint16 spp_conn_id)
{
     if(sppTest_IsInstanceValid(spp_instance_index))
    {
        spp_service_test_data[spp_instance_index]->spp_connection_id = spp_conn_id;
        DEBUG_LOG("sppTest_SetSppConnIdForIndex instance-->%d  conn id--> %d", spp_instance_index, spp_conn_id);
    }
}

#endif /* INCLUDE_SPPS_LOOPBACK_TEST */

